#include "PostgreSQLGuiLexer.hpp"
#include "parsing/tsqllexer.h"

namespace SQLLexer
{

using namespace Antlr3GuiImpl;

class postgreSQLGuiLexer : public Lexer
{
public:
    postgreSQLGuiLexer(const QString &statement, const QString &name);
    virtual ~postgreSQLGuiLexer();
    virtual QString firstWord();
    virtual QString wordAt(const Position &);
    virtual token_const_iterator findStartToken(token_const_iterator const &);    
    virtual token_const_iterator findEndToken(token_const_iterator const &);
    virtual void setStatement(const char *s, unsigned len = -1);
    virtual void setStatement(const QString &s);

	typedef PostgreSQLGuiLexerTraits::CommonTokenType CommonTokenType;
protected:
    virtual int size() const;
    virtual const Token& LA(int pos) const;

private:
    void init();
    void clean();
    QByteArray QBAinput;
    QByteArray QBAname;

    Antlr3GuiImpl::PostgreSQLGuiLexerTraits::InputStreamType*    input;
    Antlr3GuiImpl::PostgreSQLGuiLexer *lxr;
    Antlr3GuiImpl::PostgreSQLGuiLexerTraits::TokenStreamType* tstream;

    unsigned lastLine, lastColumn, lastIndex;

    mutable Token retvalLA;
};

postgreSQLGuiLexer::postgreSQLGuiLexer(const QString &statement, const QString &name)
    : Lexer(statement, name)
    , QBAinput(statement.toUtf8())
    , QBAname(name.toUtf8())
    , lastLine(1)
    , lastColumn(0)
    , lastIndex(0)
{
	init();
}

postgreSQLGuiLexer::~postgreSQLGuiLexer()
{
    clean();
}

void postgreSQLGuiLexer::init()
{
    input = new PostgreSQLGuiLexerTraits::InputStreamType(
    		(const ANTLR_UINT8 *)QBAinput.data(),
			antlr3::ENC_UTF8,
    		QBAinput.size(), //strlen(data.c_str()),
    		(ANTLR_UINT8*)QBAname.data());

    input->setUcaseLA(true); // ignore case

    if (input == NULL)
    	throw Exception();

    lxr     = new PostgreSQLGuiLexer(input);     // PostgreSQLGuiLexer is generated by ANTLR

    if ( lxr == NULL )
        throw Exception();

    tstream = new PostgreSQLGuiLexerTraits::TokenStreamType(ANTLR_SIZE_HINT, lxr->get_tokSource());

    if (tstream == NULL)
        throw Exception();
};

void postgreSQLGuiLexer::setStatement(const char *s, unsigned len)
{
	clean();
	QBAinput.clear();
	QBAinput.append(s, len);
	lastLine = 1;
	lastColumn = 0;
	lastIndex = 0;
	init();
}

void postgreSQLGuiLexer::setStatement(const QString &statement)
{
	clean();
	QBAinput.clear();
	QBAinput.append(statement.toUtf8());
	lastLine = 1;
	lastColumn = 0;
	lastIndex = 0;
	init();
}

void postgreSQLGuiLexer::clean()
{
	if( tstream)
		delete tstream;
	if( lxr)
		delete lxr;
	if( input)
		delete input;
	tstream = NULL;
	lxr = NULL;
	input = NULL;
}

int postgreSQLGuiLexer::size() const
{
	if(tstream)
		return tstream->getTokens()->size()+1;
	else
		return 0;
}

const Token& postgreSQLGuiLexer::LA(int pos) const
{
	if ( pos <= 0 || pos > size())
		throw Exception();

	if( pos == size())
	{
		// Requesting EOF_TOKEN
		if (pos == 1)
		{
			// The buffer is empty - Only EOF_TOKEN is "present"
			Token::TokenType type = Token::X_EOF;
			retvalLA = Token(Position(0, 0), 0, PostgreSQLGuiLexer::EOF_TOKEN, type);
			retvalLA.setText("EOF");
			retvalLA.setBlockContext(NONE);
			return retvalLA;
		}

		CommonTokenType const* token = tstream->get(pos-2);
		if (!token)
			throw Exception();

		int line = token->get_line() - 1;
		int column = token->get_charPositionInLine();
		unsigned length = token->get_stopIndex() - token->get_startIndex() + 1;
		QString txt = QString::fromUtf8(token->getText().c_str());
		Token::TokenType type = Token::X_EOF;
		retvalLA = Token(Position(line, column+length+1), 0, PostgreSQLGuiLexer::EOF_TOKEN, type);
		retvalLA.setText("EOF");
		retvalLA.setBlockContext(NONE);
		return retvalLA;
	}

	CommonTokenType const* token = tstream->get(pos-1);
	if(token)
	{
		// ANTLR3 starts with 1st while QScintilla starts with 0th
		int line = token->get_line() - 1;
		int column = token->get_charPositionInLine();
		unsigned length = token->get_stopIndex() - token->get_startIndex() + 1;
		int offset = token->get_startIndex();
		Token::TokenType type = Token::X_UNASSIGNED;

		switch(token->getType())
		{ 
		case PostgreSQLGuiLexer::EOF_TOKEN:
			type = Token::X_EOF;
			break;
		//case PostgreSQLGuiLexer::BUILDIN_FUNCTIONS:
		//	type = Token::L_BUILDIN;
		//	break;
		case PostgreSQLGuiLexer::CHARACTER_LITERAL:
		//case PostgreSQLGuiLexer::USER_VAR:
			type = Token::L_STRING;
			break;
		case PostgreSQLGuiLexer::COMMENT_ML:
		//case MySQLGuiLexer::COMMENT_ML_PART:
			type = Token::X_COMMENT_ML;
			break;
		//case MySQLGuiLexer::COMMENT_ML_END:
		//	type = Token::X_COMMENT_ML_END;
		//	break;
		case PostgreSQLGuiLexer::COMMENT_SL:
			type = Token::X_COMMENT;
			break;
		case PostgreSQLGuiLexer::CommonTokenType::TOKEN_EOF - 1: // See UserGuiTraits recover()
		case PostgreSQLGuiLexer::TOKEN_FAILURE:
			type = Token::X_FAILURE;
			break;
		case PostgreSQLGuiLexer::KEYWORD:
			type = Token::L_RESERVED; 
			break;
		case PostgreSQLGuiLexer::NEWLINE:
		case PostgreSQLGuiLexer::SPACE:
			type = Token::X_WHITE; 
			break;
		//case PostgreSQLGuiLexer::BIND_VAR:
		//case PostgreSQLGuiLexer::BIND_VAR_WITH_NAME:
		//	type = Token::L_BIND_VAR;
		//	break;
		case PostgreSQLGuiLexer::BIND_VAR_WITH_PARAMS:
			type = Token::L_BIND_VAR_WITH_PARAMS;
			break;			
		default:
			type = Token::X_UNASSIGNED;
			break;
		}		

	    retvalLA = Token(Position(line, column), length, token->getType(), type);
		retvalLA.setText(QString::fromUtf8(token->getText().c_str()));
	    return retvalLA;
	} else
		throw Exception();
}

QString postgreSQLGuiLexer::firstWord()
{
	PostgreSQLGuiLexerTraits::CommonTokenType const* token = tstream->LT(1);
    if( token)
    {
		return QString((const char*)(token->getText().c_str()));
    }
    return QString();
}

QString postgreSQLGuiLexer::wordAt(const Position &pos)
{
	unsigned line = pos.getLine();
	unsigned column = pos.getLinePos();

    line++; // ANTLR3 starts with 1st while QScintilla starts with 0th

    QString retval;

    return retval;
}

Lexer::token_const_iterator postgreSQLGuiLexer::findStartToken( Lexer::token_const_iterator const &start)
{
	QSet<SQLLexer::Token::TokenType> INTRODUCERS = QSet<SQLLexer::Token::TokenType>()
		<< SQLLexer::Token::L_SELECT_INTRODUCER
		<< SQLLexer::Token::L_DML_INTRODUCER
		<< SQLLexer::Token::L_DDL_INTRODUCER
		<< SQLLexer::Token::L_PL_INTRODUCER
		<< SQLLexer::Token::L_OTHER_INTRODUCER
		<< SQLLexer::Token::L_LPAREN
		<< SQLLexer::Token::X_ONE_LINE
		;	
	token_const_iterator i(start);
	if(!INTRODUCERS.contains(i->getTokenType()))
	{
		i = i.consumeUntil(INTRODUCERS);
		i++;
	}
	return i;
}

Lexer::token_const_iterator postgreSQLGuiLexer::findEndToken( Lexer::token_const_iterator const &start)
{
	token_const_iterator i( start);
	switch( i->getTokenType())
	{
	case Token::L_SELECT_INTRODUCER:
	case Token::L_DML_INTRODUCER:
	case Token::L_DDL_INTRODUCER:
	case Token::L_PL_INTRODUCER:
	case Token::L_OTHER_INTRODUCER:
		while(true)
		{
			i++;
			if( i->getTokenType() == Token::X_EOF)
				break;
			if( i->getTokenType() == Token::X_UNASSIGNED && i->getOrigTokenType() == PostgreSQLGuiLexer::SEMI)
				break;
		}
		break;
	default:
		throw new Exception();
	}

 	if( i == start) // If the statement contains only one token advance forward. (Never return the same token)
 		i++;
	return i;
}

};

Util::RegisterInFactory<SQLLexer::postgreSQLGuiLexer, LexerFactTwoParmSing> rePostgreSQLLexStatement("PostreSQLGuiLexer");
